package com.pw.websocket;

//Saco es el mas maricon
import com.pw.websocket.messages.ConnectionInfoMessage;
import com.pw.websocket.messages.MessageInfoMessage;
import com.pw.websocket.messages.StatusInfoMessage;
import com.google.gson.Gson;
/*
* StreamInbound: 
* Base implementation of the class used to process WebSocket connections 
* based on streams. Applications should extend this class to provide application 
* specific functionality. Applications that wish to operate on a message basis rather
* than a stream basis should use MessageInbound.
* 
* MessageInbound: 
* Base implementation of the class used to process WebSocket connections based on messages.
* Applications should extend this class to provide application specific functionality. 
* Applications that wish to operate on a stream basis rather than a message basis should use StreamInbound.
* 
* 	onOpen()
	protected void onOpen(WsOutbound outbound)
	Intended to be overridden by sub-classes that wish to be notified when the outbound connection is established. 
* 	The default implementation is a NO-OP.
* 
* 	onClose()
	protected void onClose(int status)
	Intended to be overridden by sub-classes that wish to be notified when the outbound connection is closed. 
* 	The default implementation is a NO-OP.
* 
* WebSocketServlet: 
* Provides the base implementation of a Servlet for processing WebSocket connections as per RFC6455. 
* It is expected that applications will extend this implementation and provide application specific functionality.
*	public abstract class WebSocketServlet extends HttpServlet
*
* WsOutbound:
* public class WsOutbound extends Object
* provee los medios para escribir mensajes websockets al cliente.
* Provides the means to write WebSocket messages to the client. All methods that write 
* to the client (or update a buffer that is later written to the client) are synchronized 
* to prevent multiple threads trying to write to the client at the same time.
* 
* 	getWsOutbound() 
*	 Obtain the outbound side of this WebSocket connection used for writing data to the client.
 */
import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.catalina.websocket.WsOutbound;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/*
 * HttpServletRequest:
 * public interface HttpServletRequest extends ServletRequest
 * Extends the ServletRequest interface to provide request information for HTTP servlets.
 *
 * The servlet container creates an HttpServletRequest object and passes it as an 
 * argument to the servlet's service methods (doGet, doPost, etc).
 *
 * 	getSession(boolean create) 
 * 	Returns the current HttpSession associated with this request or, if if there 
 * 	is no current session and create is true, returns a new session.
 * 
 * 	getParameter(java.lang.String name) 
 * 	Returns the value of a request parameter as a String, or null if the parameter does not exist.
 */
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.*;

@WebServlet(urlPatterns = "/chat")
public class WebSocketCharServlet extends WebSocketServlet {
	/*
		The LoggerFactory is a utility class producing Loggers for various logging APIs, 
		most notably for log4j, logback and JDK 1.4 logging. Other implementations such as 
		NOPLogger and SimpleLogger are also supported.
		
			getLogger(Class clazz) 
			Return a logger named corresponding to the class passed as parameter, 
			using the statically bound ILoggerFactory instance.
	*/ 
    private static final Logger log = LoggerFactory.getLogger(WebSocketCharServlet.class);
	
    private static final Map<String, ChatConnection> connections = new HashMap<String, ChatConnection>();

    @Override
    protected boolean verifyOrigin(String origin) {
        return true;
    }

/*
 * createWebSocketInbound(String subProtocol, HttpServletRequest request) 
 * Create the instance that will process this inbound connection. 
 * streamboud conexion, datos entrantes conjunto de messagebound
 */

    @Override
    protected StreamInbound createWebSocketInbound(String subProtocol, HttpServletRequest request) {
        final String connectionId = request.getSession().getId();
        final String userName = request.getParameter("userName");
        return new ChatConnection(connectionId, userName);
    }

    private static class ChatConnection extends MessageInbound {

        private final String connectionId;

        private final String userName;

        private final Gson jsonProcessor;

        private ChatConnection(String connectionId, String userName) {
            this.connectionId = connectionId;
            this.userName = userName;
            this.jsonProcessor = new Gson();
        }

        @Override
        protected void onOpen(WsOutbound outbound) {
            sendConnectionInfo(outbound);
            sendStatusInfoToOtherUsers(new StatusInfoMessage(userName, StatusInfoMessage.STATUS.CONNECTED));
            connections.put(connectionId, this);
        }

        @Override
        protected void onClose(int status) {
            sendStatusInfoToOtherUsers(new StatusInfoMessage(userName, StatusInfoMessage.STATUS.DISCONNECTED));
            connections.remove(connectionId);
        }
		
		/*
		 * onBinaryMessage(ByteBuffer message) 
         * This method is called when there is a binary WebSocket message available to process.
		 */
		 
        @Override
        protected void onBinaryMessage(ByteBuffer byteBuffer) throws IOException {
            throw new UnsupportedOperationException("Binary messages not supported");
        }
		
		/*
		* onTextMessage(CharBuffer message) 
        * This method is called when there is a textual WebSocket message available to process.
        */ 
        @Override
        protected void onTextMessage(CharBuffer charBuffer) throws IOException {
            final MessageInfoMessage message = jsonProcessor.fromJson(charBuffer.toString(), MessageInfoMessage.class);
            final ChatConnection destinationConnection = getDestinationUserConnection(message.getMessageInfo().getTo());
            if (destinationConnection != null) {
                final CharBuffer jsonMessage = CharBuffer.wrap(jsonProcessor.toJson(message));
                destinationConnection.getWsOutbound().writeTextMessage(jsonMessage);
                /* destinationconnection is chatconection that extends messageoutbound extends from streamoutbound*/
            } else {
                log.warn("Se está intentando enviar un mensaje a un usuario no conectado");
            }
        }

        public String getUserName() {
            return userName;
        }

        private void sendConnectionInfo(WsOutbound outbound) {
            final List<String> activeUsers = getActiveUsers();
            final ConnectionInfoMessage connectionInfoMessage = new ConnectionInfoMessage(userName, activeUsers);
            try {
                outbound.writeTextMessage(CharBuffer.wrap(jsonProcessor.toJson(connectionInfoMessage)));
            } catch (IOException e) {
                log.error("No se pudo enviar el mensaje", e);
            }
        }

        private List<String> getActiveUsers() {
            final List<String> activeUsers = new ArrayList<String>();
            for (ChatConnection connection : connections.values()) {
                activeUsers.add(connection.getUserName());
            }
            return activeUsers;
        }

        private void sendStatusInfoToOtherUsers(StatusInfoMessage message) {
            final Collection<ChatConnection> otherUsersConnections = getAllChatConnectionsExceptThis();
            for (ChatConnection connection : otherUsersConnections) {
                try {
                    connection.getWsOutbound().writeTextMessage(CharBuffer.wrap(jsonProcessor.toJson(message)));
                } catch (IOException e) {
                    log.error("No se pudo enviar el mensaje", e);
                }
            }
        }

        private Collection<ChatConnection> getAllChatConnectionsExceptThis() {
            final Collection<ChatConnection> allConnections = connections.values();
            allConnections.remove(this);
            return allConnections;
        }

        private ChatConnection getDestinationUserConnection(String destinationUser) {
            for (ChatConnection connection : connections.values()) {
                if (destinationUser.equals(connection.getUserName())) {
                    return connection;
                }
            }
            return null;
        }

    }

}
